package com.uuah.server.transport;

import java.beans.ExceptionListener;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uuah.api.work.IWorkManager;
import com.uuah.concurrent.WaitableBoolean;
import com.uuah.exception.AbstractUuahException;
import com.uuah.exception.lifecycle.InitialisationException;
import com.uuah.server.api.endpoint.IUuahEndpoint;
import com.uuah.server.api.transport.IConnectable;
import com.uuah.server.api.transport.IConnector;
import com.uuah.server.api.transport.retry.IRetryCallback;
import com.uuah.server.api.transport.retry.IRetryContext;
import com.uuah.server.api.transport.retry.RetryPolicyTemplate;
import com.uuah.server.context.notification.ConnectionNotification;
import com.uuah.server.exception.transport.ConnectException;
import com.uuah.utils.ClassUtils;

/**
 *
 * TODO comment ...
 *
 * <p>
 * update record:updator，update time，update content and version
 * </p>
 *
 * @author <a href="jonny_quan@hotmail.com">jonny</a>
 * @date 2009 2009-3-27 下午02:49:09
 * @version 1.0.0
 */
public abstract class AbstractConnectable implements IConnectable,
		ExceptionListener {
	protected transient Logger logger = LoggerFactory.getLogger(getClass());

	protected IUuahEndpoint endpoint;
	protected final AbstractConnector connector;

	protected final AtomicBoolean disposing = new AtomicBoolean(false);
	protected final AtomicBoolean disposed = new AtomicBoolean(false);

	protected RetryPolicyTemplate retryTemplate;

	protected final AtomicBoolean connecting = new AtomicBoolean(false);
	protected final WaitableBoolean connected = new WaitableBoolean(false);

	protected final WaitableBoolean stopped = new WaitableBoolean(true);

	protected boolean asyncConnections = false;

	protected boolean startOnConnect = false;

	protected boolean useStrictConnectDisconnect = false;

	public AbstractConnectable(IUuahEndpoint endpoint) {
		this.endpoint = endpoint;
		this.connector = (AbstractConnector) endpoint.getConnector();
	}

	protected void disposeAndLogException() {
		try {
			dispose();
		} catch (Throwable t) {
			logger.error("Could not dispose of the message dispatcher!", t);
		}
	}

	public void exceptionThrown(Exception e) {
		try {
			getConnector().handleException(e);
		} finally {
			dispose();
		}
	}

	public void handleException(Exception exception) {
		if (exception instanceof ConnectException) {
			logger
					.info("Exception caught is a ConnectException, disconnecting receiver and invoking ReconnectStrategy");
			try {
				disconnect();
			} catch (Exception e) {
				connector.getExceptionListener().exceptionThrown(e);
			}
		}
		connector.getExceptionListener().exceptionThrown(exception);
		if (exception instanceof ConnectException) {
			try {
				logger.warn("Reconnecting after exception: "
						+ exception.getMessage(), exception);
				connect();
			} catch (Exception e) {
				connector.getExceptionListener().exceptionThrown(e);
			}
		}
	}

	public boolean validate() {
		return !disposed.get();
	}

	public void activate() {
	}

	public void passivate() {
	}

	public void initialise() throws InitialisationException {
		if (endpoint.getRetryPolicyTemplate() != null) {
			retryTemplate = endpoint.getRetryPolicyTemplate();
		} else {
			retryTemplate = connector.getRetryPolicyTemplate();
		}
	}

	public synchronized void dispose() {
		if (!disposed.get()) {
			try {
				this.disconnect();
				this.stop();
			} catch (Exception e) {
				logger.warn(e.getMessage(), e);
			}
		}
	}

	public IConnector getConnector() {
		return connector;
	}

	public IUuahEndpoint getEndpoint() {
		return endpoint;
	}

	public final synchronized void connect() throws Exception {
		if (disposed.get()) {
			throw new IllegalStateException(
					"dispatcher has been disposed; cannot connect to resource");
		}

		retryTemplate.execute(new IRetryCallback() {
			public void doWork(IRetryContext context) throws Exception {
				try {
					doConnect();
				} catch (Exception e) {
					if (logger.isDebugEnabled()) {
						e.printStackTrace();
					}
					throw e;
				}
				connected.set(true);
			}

			public String getWorkDescription() {
				return getConnectionDescription();
			}
		});

		connected.whenTrue(null);

		if (startOnConnect) {
			start();
		}
	}

	public final synchronized void disconnect() throws Exception {
		if (!connected.get()) {
			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Disconnecting: " + this);
		}

		this.doDisconnect();
		connected.set(false);

		logger.info("Disconnected: " + this);

		connector.fireNotification(new ConnectionNotification(this,
				getConnectEventId(endpoint),
				ConnectionNotification.CONNECTION_DISCONNECTED));
	}

	protected String getConnectEventId(IUuahEndpoint endpoint) {
		return connector.getName() + ".dispatcher("
				+ endpoint.getEndpointURI().getUri() + ")";
	}

	public final boolean isConnected() {
		return connected.get();
	}

	protected boolean isDoThreading() {
		return connector.getDispatcherThreadingProfile().isDoThreading();
	}

	public String getConnectionDescription() {
		return "endpoint.response." + endpoint.getEndpointURI().toString();
	}

	public synchronized void reconnect() throws Exception {
		disconnect();
		connect();
	}

	public final void start() throws AbstractUuahException {
		if (!connected.get()) {
			startOnConnect = true;
			return;
		}

		if (stopped.compareAndSet(true, false)) {
			doStart();
		}
	}

	public final void stop() {
		try {
			if (connected.get()) {
				disconnect();
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		if (stopped.compareAndSet(false, true)) {
			try {
				doStop();
			} catch (AbstractUuahException e) {
				logger.error(e.getMessage(), e);
			}

		}
	}

	protected void doInitialise() throws InitialisationException {
		// nothing to do by default
	}

	protected void doDispose() {
		// nothing to do by default
	}

	protected void doConnect() throws Exception {
		// nothing to do by default
	}

	protected void doDisconnect() throws Exception {
		// nothing to do by default
	}

	protected void doStart() throws AbstractUuahException {
		// nothing to do by default
	}

	protected void doStop() throws AbstractUuahException {
		// nothing to do by default
	}

	public String toString() {
		final StringBuffer sb = new StringBuffer(80);
		sb.append(ClassUtils.getSimpleName(this.getClass()));
		sb.append("{this=").append(
				Integer.toHexString(System.identityHashCode(this)));
		sb.append(", endpoint=").append(endpoint.getEndpointURI().getUri());
		sb.append(", disposed=").append(disposed);
		sb.append('}');
		return sb.toString();
	}

	public void setEndpoint(IUuahEndpoint endpoint) {
		if (endpoint == null) {
			throw new IllegalArgumentException("Endpoint cannot be null");
		}
		this.endpoint = endpoint;
	}

	abstract protected IWorkManager getWorkManager();
}